using Sidvall.CodeGenerator.DataObjects;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Xml.Schema;

namespace Sidvall.CodeGenerator
{
    public class XsdHelper
    {
        #region Public Members

        public XsdImportSettings Settings { get; set; }

        #region NewDomainModel

        public static DomainModel NewDomainModel(string fileName)
        {
            return NewDomainModel(fileName, null);
        }

        public static DomainModel NewDomainModel(string fileName, XsdImportSettings settings)
        {
            XsdHelper helper;

            if (settings == null)
                settings = new XsdImportSettings();
            helper = new XsdHelper()
            {
                FileName = fileName,
                RootPath = System.IO.Path.GetDirectoryName(fileName),
                Settings = settings,
            };

            return helper.Parse();
        }

        #endregion
        #region SetLocalSchemaLocation

        public static void SetLocalSchemaLocation(System.Collections.Generic.IEnumerable<string> files)
        {
            string fileContent, fileName, namePrefix;
            int index;

            foreach (var file in files)
            {
                fileContent = System.IO.File.ReadAllText(file);
                fileName = System.IO.Path.GetFileNameWithoutExtension(file);
                index = fileName.LastIndexOf('.');
                if (index >= 0)
                    namePrefix = fileName.Substring(0, index) + ".";
                else
                    namePrefix = string.Empty;

                // Rename schemaLocation
                fileContent = SetLocalSchemaLocation(file, namePrefix, fileContent, "import");
                fileContent = SetLocalSchemaLocation(file, namePrefix, fileContent, "include");

                System.IO.File.WriteAllText(file, fileContent);
            }
        }
        private static string SetLocalSchemaLocation(string filePath, string namePrefix, string text, string attribute)
        {
            string oldFileName, newFileName, newFilePath;
            int startIndex, index, index1, index2;

            startIndex = NextStartIndex(text, attribute, 0);
            while (startIndex >= 0)
            {
                index1 = text.IndexOf(@"schemaLocation=", startIndex + 1, System.StringComparison.OrdinalIgnoreCase);
                index2 = text.IndexOf("\"", index1 + 16, System.StringComparison.OrdinalIgnoreCase);
                oldFileName = text.Substring(index1 + 16, index2 - index1 - 16);
                if (oldFileName.StartsWith("http://"))
                {
                    newFileName = GetNamespace(oldFileName);
                    newFilePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(filePath), newFileName);
                    if (!System.IO.File.Exists(newFilePath))
                    {
                        index = oldFileName.LastIndexOf("/");
                        if (index >= 0)
                            newFileName = oldFileName.Substring(index + 1);
                    }
                }
                else
                {
                    newFileName = GetNamespace(namePrefix + oldFileName);
                    newFilePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(filePath), newFileName);
                    if (!System.IO.File.Exists(newFilePath))
                    {
                        newFileName = GetNamespace(oldFileName);
                    }
                }
                if (oldFileName != newFileName)
                {
                    newFilePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(filePath), newFileName);
                    if (System.IO.File.Exists(newFilePath))
                    {
                        text = text.Substring(0, index1 + 16)
                            + newFileName
                            + text.Substring(index2);
                    }
                }
                startIndex = NextStartIndex(text, attribute, startIndex + 8);
            }
            return text;
        }

        #endregion

        #endregion
        #region Helper Members

        private string FileName { get; set; }
        private string RootPath { get; set; }
        #region SchemaFiles

        private HashSet<string> _SchemaFiles;
        private HashSet<string> SchemaFiles
        {
            get
            {
                if (_SchemaFiles == null)
                    _SchemaFiles = new HashSet<string>();
                return _SchemaFiles;
            }
        }

        #endregion
        #region Schemas

        private List<XmlSchema> _Schemas;
        private List<XmlSchema> Schemas
        {
            get
            {
                if (_Schemas == null)
                    _Schemas = new List<XmlSchema>();
                return _Schemas;
            }
        }

        #endregion

        #region GetNamespace

        private static string GetNamespace(string schemaFileUrl)
        {
            return schemaFileUrl.Replace("http://", "").Replace("/", ".");
        }

        #endregion
        #region LoadSchema

        private XmlSchema LoadSchema(string schemaFilePath)
        {
            XmlSchema importedSchema;
            string path, fileUrl;
            int index;

            if (this.SchemaFiles.Contains(schemaFilePath))
                return null;
            this.SchemaFiles.Add(schemaFilePath);

            using (var s = File.OpenRead(schemaFilePath))
            {
                importedSchema = XmlSchema.Read(s, null);
                this.Schemas.Add(importedSchema);
                foreach (var include in importedSchema.Includes)
                {
                    if (include is XmlSchemaInclude)
                    {
                        path = (include as XmlSchemaInclude).SchemaLocation;
                        if (!System.IO.Path.IsPathRooted(path))
                            path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(schemaFilePath), path);
                        LoadSchema(path);
                    }
                    else if (include is XmlSchemaImport)
                    {
                        path = (include as XmlSchemaImport).SchemaLocation;
                        if (path.StartsWith("http", System.StringComparison.OrdinalIgnoreCase))
                        {
                            fileUrl = path;
                            path = null;
                        }
                        else
                        {
                            index = schemaFilePath.LastIndexOf('/');
                            fileUrl = schemaFilePath.Substring(0, index + 1) + path;
                        }
                        LoadSchemaFromUrl(fileUrl, path);
                    }
                }
            }

            return importedSchema;
        }

        #endregion
        #region LoadSchemaFromUrl

        private XmlSchema LoadSchemaFromUrl(string schemaFileUrl, string fileName)
        {
            XmlSchema importedSchema;
            string path, fileUrl, schemaFilePath;
            int index;

            fileName = GetNamespace(schemaFileUrl);
            schemaFilePath = System.IO.Path.Combine(this.RootPath, fileName);
            if (!System.IO.File.Exists(schemaFilePath))
            {
                HttpWebRequest fileReq = (HttpWebRequest)HttpWebRequest.Create(schemaFileUrl);
                HttpWebResponse fileResp = (HttpWebResponse)fileReq.GetResponse();
                using (Stream s = fileResp.GetResponseStream())
                {
                    using (var writeStream = File.Create(schemaFilePath))
                    {
                        byte[] buffer = new byte[1024];
                        int bytesRead;

                        // Simple do/while loop to read from stream until
                        // no bytes are returned
                        do
                        {
                            // Read data (up to 1k) from the stream
                            bytesRead = s.Read(buffer, 0, buffer.Length);

                            // Write the data to the local file
                            writeStream.Write(buffer, 0, bytesRead);
                        } while (bytesRead > 0);
                    }
                }
            }
            else
            {

            }

            if (this.SchemaFiles.Contains(schemaFilePath))
                return null;
            this.SchemaFiles.Add(schemaFilePath);

            using (var s = File.OpenRead(schemaFilePath))
            {
                importedSchema = XmlSchema.Read(s, null);
                this.Schemas.Add(importedSchema);
                foreach (var include in importedSchema.Includes)
                {
                    if (include is XmlSchemaInclude)
                    {
                        path = (include as XmlSchemaInclude).SchemaLocation;
                    }
                    else if (include is XmlSchemaImport)
                    {
                        path = (include as XmlSchemaImport).SchemaLocation;
                    }
                    else
                        path = null;
                    if (path != null)
                    {
                        if (path.StartsWith("http", System.StringComparison.OrdinalIgnoreCase))
                        {
                            fileUrl = path;
                            path = null;
                        }
                        else
                        {
                            index = schemaFileUrl.LastIndexOf('/');
                            fileUrl = schemaFileUrl.Substring(0, index + 1) + path;
                        }
                        LoadSchemaFromUrl(fileUrl, path);
                    }
                }
            }

            return importedSchema;
        }

        #endregion
        #region NextStartIndex

        private static int NextStartIndex(string text, string attribute, int startIndex)
        {
            int index;

            if (startIndex > 0)
            {
                index = text.IndexOf(@"<" + attribute, startIndex, System.StringComparison.OrdinalIgnoreCase);
                if (index < 0)
                {
                    index = text.IndexOf(@":" + attribute, startIndex, System.StringComparison.OrdinalIgnoreCase);
                }
            }
            else
            {
                index = text.IndexOf(@"<" + attribute, System.StringComparison.OrdinalIgnoreCase);
                if (index < 0)
                {
                    index = text.IndexOf(@":" + attribute, System.StringComparison.OrdinalIgnoreCase);
                }
            }
            return index;
        }

        #endregion
        #region Parse

        private DomainModel Parse()
        {
            DomainModel domainModel;
            DomainObject item;
            Schema schema;
            NamespaceMapping namespaceMapping;
            string targetNamespace;

            // Load xsd-files
            LoadSchema(this.FileName);
            schema = new Schema();
            foreach (var fil in this.SchemaFiles)
                schema.Import(www.w3.org.Item2001.XMLSchema.schema.Load(fil), System.IO.Path.GetFileNameWithoutExtension(fil));

            // Update SchemaLocation
            if (this.Settings.SetLocalSchemaLocation)
            {
                XsdHelper.SetLocalSchemaLocation(this.SchemaFiles);
            }

            // Initialize domain model
            domainModel = new DomainModel();
            foreach (var simpleType in schema.SimpleTypes)
            {
                if ((simpleType.Item.Content.restriction != null) && (simpleType.Item.Content.restriction.enumeration != null) && (simpleType.Item.Content.restriction.enumeration.Count > 0))
                {
                    if (simpleType.Parent.targetNamespace != null)
                    {
                        targetNamespace = simpleType.Parent.targetNamespace.ToString();
                        if (this.Settings.NamespaceMappings.Contains(targetNamespace))
                            namespaceMapping = this.Settings.NamespaceMappings[targetNamespace];
                        else
                            namespaceMapping = null;
                    }
                    else
                    {
                        targetNamespace = null;
                        namespaceMapping = null;
                    }
                    if ((namespaceMapping != null) && (namespaceMapping.Ignore))
                        continue;

                    item = new DomainObject()
                    {
                        Name = simpleType.Item.Content.name,
                        NameInDataStore = simpleType.Item.Content.name,
                        NamespaceInDataStore = targetNamespace,
                        Namespace = (namespaceMapping != null) ? namespaceMapping.NewNamespace : GetNamespace(simpleType.FileName),
                    };
                    foreach (var enumValue in simpleType.Item.Content.restriction.enumeration)
                        item.Properties.Add(enumValue.Content.value, enumValue.Content.id);
                    domainModel.DatabaseDomainObjects.Add(item);
                }
            }
            foreach (var element in schema.Elements)
            {
                item = new DomainObject()
                {
                    Name = element.Item.Content.name,
                    NameInDataStore = element.Item.Content.name
                };
                domainModel.DatabaseDomainObjects.Add(item);
            }

            return domainModel;
        }

        #endregion

        #endregion
        #region Constructors

        private XsdHelper()
        {
        }

        #endregion
    }
}
